home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / sbin / update-apt-xapian-index < prev    next >
Text File  |  2009-07-14  |  25KB  |  742 lines

  1. #!/usr/bin/python
  2.  
  3. #
  4. # update-apt-xapian-index - Maintain a system-wide Xapian index of Debian
  5. #                           package information
  6. #
  7. # Copyright (C) 2007  Enrico Zini <enrico@debian.org>
  8. #
  9. # This program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation; either version 2 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  22. #
  23.  
  24. import os
  25.  
  26. # Setup configuration
  27. PLUGINDIR = os.environ.get("AXI_PLUGIN_DIR", "/usr/share/apt-xapian-index/plugins")
  28. XAPIANDBPATH = os.environ.get("AXI_DB_PATH", "/var/lib/apt-xapian-index")
  29. XAPIANDBSTAMP = XAPIANDBPATH + "/update-timestamp"
  30. XAPIANDBLOCK = XAPIANDBPATH + "/update-lock"
  31. XAPIANDBUPDATESOCK = XAPIANDBPATH + "/update-socket"
  32. XAPIANDBVALUES = XAPIANDBPATH + "/values"
  33. XAPIANDBDOC = XAPIANDBPATH + "/README"
  34.  
  35. class Progress:
  36.     def __init__(self):
  37.         self.task = None
  38.         self.halfway = False
  39.         self.is_verbose = False
  40.     def begin(self, task):
  41.         self.task = task
  42.         print "%s..." % self.task,
  43.         sys.stdout.flush()
  44.         self.halfway = True
  45.     def progress(self, percent):
  46.         print "\r%s... %d%%" % (self.task, percent),
  47.         sys.stdout.flush()
  48.         self.halfway = True
  49.     def end(self):
  50.         print "\r%s: done.  " % self.task
  51.         self.halfway = False
  52.     def verbose(self, *args):
  53.         if not self.is_verbose: return
  54.         if self.halfway:
  55.             print
  56.         print " ".join(args)
  57.         self.halfway = False
  58.     def notice(self, *args):
  59.         if self.halfway:
  60.             print
  61.         print >>sys.stderr, " ".join(args)
  62.         self.halfway = False
  63.     def warning(self, *args):
  64.         if self.halfway:
  65.             print
  66.         print >>sys.stderr, " ".join(args)
  67.         self.halfway = False
  68.     def error(self, *args):
  69.         if self.halfway:
  70.             print
  71.         print >>sys.stderr, " ".join(args)
  72.         self.halfway = False
  73.  
  74. class BatchProgress:
  75.     def __init__(self):
  76.         self.task = None
  77.     def begin(self, task):
  78.         self.task = task
  79.         print "begin: %s\n" % self.task,
  80.         sys.stdout.flush()
  81.     def progress(self, percent):
  82.         print "progress: %d/100\n" % percent,
  83.         sys.stdout.flush()
  84.     def end(self):
  85.         print "done: %s\n" % self.task
  86.         sys.stdout.flush()
  87.     def verbose(self, *args):
  88.         print "verbose: %s" % (" ".join(args))
  89.         sys.stdout.flush()
  90.     def notice(self, *args):
  91.         print "notice: %s" % (" ".join(args))
  92.         sys.stdout.flush()
  93.     def warning(self, *args):
  94.         print "warning: %s" % (" ".join(args))
  95.         sys.stdout.flush()
  96.     def error(self, *args):
  97.         print "error: %s" % (" ".join(args))
  98.         sys.stdout.flush()
  99.  
  100. class SilentProgress:
  101.     def begin(self, task):
  102.         pass
  103.     def progress(self, percent):
  104.         pass
  105.     def end(self):
  106.         pass
  107.     def verbose(self, *args):
  108.         pass
  109.     def notice(self, *args):
  110.         pass
  111.     def warning(self, *args):
  112.         print >>sys.stderr, " ".join(args)
  113.     def error(self, *args):
  114.         print >>sys.stderr, " ".join(args)
  115.  
  116. class ClientProgress:
  117.     def __init__(self, progress):
  118.         self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  119.         self.sock.settimeout(None)
  120.         self.sock.connect(XAPIANDBUPDATESOCK)
  121.         self.progress = progress
  122.  
  123.     def loop(self):
  124.         hasBegun = False
  125.         while True:
  126.             msg = self.sock.recv(4096)
  127.             try:
  128.                 args = pickle.loads(msg)
  129.             except EOFError:
  130.                 progress.error("The other update has stopped")
  131.                 return
  132.             action = args[0]
  133.             args = args[1:]
  134.             if action == "begin":
  135.                 progress.begin(*args)
  136.                 hasBegun = True
  137.             elif action == "progress":
  138.                 if not hasBegun:
  139.                     progress.begin(args[0])
  140.                     hasBegun = True
  141.                 progress.progress(*args[1:])
  142.             elif action == "end":
  143.                 if not hasBegun:
  144.                     progress.begin(args[0])
  145.                     hasBegun = True
  146.                 progress.end(*args[1:])
  147.             elif action == "verbose":
  148.                 progress.verbose(*args)
  149.             elif action == "notice":
  150.                 progress.notice(*args)
  151.             elif action == "error":
  152.                 progress.error(*args)
  153.             elif action == "alldone":
  154.                 break
  155.             else:
  156.                 progress.error("unknown action '%s' from other update-apt-xapian-index.  Arguments: '%s'" % (action, ", ".join(map(repr, args))))
  157.  
  158.  
  159. class ServerSenderProgress:
  160.     def __init__(self, sock, task = None):
  161.         self.sock = sock
  162.         self.task = task
  163.     def __del__(self):
  164.         self._send(pickle.dumps(("alldone",)))
  165.     def _send(self, text):
  166.         try:
  167.             self.sock.send(text)
  168.         except:
  169.             pass
  170.     def begin(self, task):
  171.         self.task = task
  172.         self._send(pickle.dumps(("begin", self.task)))
  173.     def progress(self, percent):
  174.         self._send(pickle.dumps(("progress", self.task, percent)))
  175.     def end(self):
  176.         self._send(pickle.dumps(("end", self.task)))
  177.     def verbose(self, *args):
  178.         self._send(pickle.dumps(("verbose",) + args))
  179.     def notice(self, *args):
  180.         self._send(pickle.dumps(("notice",) + args))
  181.     def warning(self, *args):
  182.         self._send(pickle.dumps(("warning",) + args))
  183.     def error(self, *args):
  184.         self._send(pickle.dumps(("error",) + args))
  185.  
  186. class ServerProgress:
  187.     def __init__(self, mine):
  188.         self.task = None
  189.         self.proxied = [mine]
  190.         self.sockfile = XAPIANDBUPDATESOCK
  191.         try:
  192.             os.unlink(self.sockfile)
  193.         except OSError:
  194.             pass
  195.         self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  196.         self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  197.         self.sock.bind(XAPIANDBUPDATESOCK)
  198.         self.sock.setblocking(False)
  199.         self.sock.listen(5)
  200.         # Disallowing unwanted people to mess with the file is automatic, as
  201.         # the socket has the ownership of the user we're using, and people
  202.         # can't connect to it unless they can write to it
  203.     def __del__(self):
  204.         self.sock.close()
  205.         os.unlink(self.sockfile)
  206.     def _check(self):
  207.         try:
  208.             sock = self.sock.accept()[0]
  209.             self.proxied.append(ServerSenderProgress(sock, self.task))
  210.         except socket.error, e:
  211.             if e.args[0] != errno.EAGAIN:
  212.                 raise
  213.         pass
  214.     def begin(self, task):
  215.         self._check()
  216.         self.task = task
  217.         for x in self.proxied: x.begin(task)
  218.     def progress(self, percent):
  219.         self._check()
  220.         for x in self.proxied: x.progress(percent)
  221.     def end(self):
  222.         self._check()
  223.         for x in self.proxied: x.end()
  224.     def verbose(self, *args):
  225.         self._check()
  226.         for x in self.proxied: x.verbose(*args)
  227.     def notice(self, *args):
  228.         self._check()
  229.         for x in self.proxied: x.notice(*args)
  230.     def warning(self, *args):
  231.         self._check()
  232.         for x in self.proxied: x.warning(*args)
  233.     def error(self, *args):
  234.         self._check()
  235.         for x in self.proxied: x.error(*args)
  236.  
  237.  
  238. class Addon:
  239.     def __init__(self, file):
  240.         self.name = os.path.basename(file)
  241.         self.name = os.path.splitext(self.name)[0]
  242.         self.filename = os.path.basename(file)
  243.         self.module = imp.load_source(self.name, file)
  244.         self.obj = self.module.init()
  245.         if self.obj:
  246.             self.info = self.obj.info()
  247.  
  248. class ExecutionTime(object):
  249.     """
  250.     Helper that can be used in with statements to have a simple
  251.     measure of the timming of a particular block of code, e.g.
  252.     with ExecutinTime("db flush"):
  253.         db.flush()
  254.     """
  255.     import time
  256.     def __init__(self, info=""):
  257.         self.info = info
  258.     def __enter__(self):
  259.         self.now = time.time()
  260.     def __exit__(self, type, value, stack):
  261.         print "%s: %s" % (self.info, time.time() - self.now)
  262.  
  263.  
  264. #
  265. # Function definitions
  266. #
  267.  
  268. def readPlugins(plugindir, progress):
  269.     """
  270.     Read the addons, in sorted order
  271.     """
  272.     addons = []
  273.     for file in sorted(glob.glob(PLUGINDIR+"/*.py")):
  274.         progress.verbose("Reading plugin %s." % file)
  275.         # Skip non-files and hidden files
  276.         if not os.path.isfile(file) or file[0] == '.':
  277.             continue;
  278.         addon = Addon(file)
  279.         if addon.obj != None:
  280.             addons.append(addon)
  281.     return addons
  282.  
  283. def get_document(pkg, addons):
  284.     """
  285.     Get a xapian.Document for the given pkg and with given
  286.     addons
  287.     """
  288.     document = xapian.Document()
  289.     # The document data is the package name
  290.     document.set_data(pkg.name)
  291.     # add information about the version of the package in slot 0
  292.     document.add_value(0, pkg.candidate.version)
  293.     # Index the package name with a special prefix, to be able to find this
  294.     # document by exact package name match
  295.     document.add_term("XP"+pkg.name)
  296.     # Have all the various plugins index their things
  297.     for addon in addons:
  298.         addon.obj.index(document, pkg)
  299.     return document
  300.  
  301. def compareCacheToDb(cache, db, progress):
  302.     """
  303.     Compare the apt cache to the database and return dicts
  304.     of the form (pkgname, docid) for the following states:
  305.  
  306.     unchanged - no new version since the last update
  307.     outdated - a new version since the last update
  308.     obsolete - no longer in the apt cache
  309.     """
  310.     unchanged = {}
  311.     outdated = {}
  312.     obsolete = {}
  313.     progress.begin("Reading Xapian index")
  314.     count = db.get_doccount()
  315.     for (idx, m) in enumerate(db.postlist("")):
  316.         if idx % 5000 == 0: progress.progress(100*idx/count)
  317.         doc = db.get_document(m.docid)
  318.         pkg = doc.get_data()
  319.         # this will return '' if there is no value 0, which is fine because it
  320.         # will fail the comparison with the candidate version causing a reindex
  321.         dbver = doc.get_value(0)
  322.         # check if the package no longer exists
  323.         if not cache.has_key(pkg) or not cache[pkg].candidate:
  324.             obsolete[pkg] = m.docid
  325.         # check if we have a new version, we do not have to delete
  326.         # the record, 
  327.         elif cache[pkg].candidate.version != dbver:
  328.             outdated[pkg] = m.docid
  329.         # its a valid package and we know about it already
  330.         else:
  331.             unchanged[pkg] = m.docid
  332.     progress.end()
  333.     return unchanged, outdated, obsolete
  334.  
  335. def updateIndex(pathname, addons, progress):
  336.     """
  337.     Update the index
  338.     """
  339.     db = xapian.WritableDatabase(pathname, xapian.DB_CREATE_OR_OPEN)
  340.     cache = apt.Cache(memonly=True)
  341.     count = len(cache)
  342.  
  343.     unchanged, outdated, obsolete = compareCacheToDb(cache, db, progress)
  344.     progress.verbose("Unchanged versions: %s, oudated version: %s, "
  345.                      "obsolete versions: %s" % (len(unchanged),
  346.                                                 len(outdated),
  347.                                                 len(obsolete)))
  348.  
  349.     progress.begin("Update Xapian index")
  350.     for idx, pkg in enumerate(cache):
  351.         if idx % 1000 == 0: progress.progress(100*idx/count)
  352.         if not pkg.candidate:
  353.             continue
  354.         if pkg.name in unchanged:
  355.             continue
  356.         elif pkg.name in outdated:
  357.             # update the existing
  358.             db.replace_document(outdated[pkg.name],
  359.                                 get_document(pkg, addons))
  360.         else:
  361.             # add the new ones
  362.             db.add_document(get_document(pkg, addons))
  363.  
  364.     # and remove the obsoletes
  365.     for docid in obsolete.values():
  366.         db.delete_document(docid)
  367.  
  368.     # finished
  369.     db.flush()
  370.     progress.end()
  371.     return True
  372.  
  373. def buildIndex(pathname, addons, progress):
  374.     """
  375.     Create a new Xapian index with the content provided by the addons
  376.     """
  377.     progress.begin("Rebuilding Xapian index")
  378.  
  379.     # Create a new Xapian index
  380.     db = xapian.WritableDatabase(pathname, xapian.DB_CREATE_OR_OVERWRITE)
  381.     # It seems to be faster without transactions, at the moment
  382.     #db.begin_transaction(False)
  383.  
  384.     # Iterate all Debian packages
  385.  
  386.     # force apt to not write a pkgcache.bin
  387.     cache = apt.Cache(memonly=True)
  388.     count = len(cache)
  389.     for idx, pkg in enumerate(cache):
  390.         if not pkg.candidate:
  391.             continue
  392.         # Print progress
  393.         if idx % 200 == 0: progress.progress(100*idx/count)
  394.         # Add the document to the index
  395.         db.add_document(get_document(pkg, addons))
  396.     #db.commit_transaction();
  397.     db.flush()
  398.     progress.end()
  399.  
  400. def buildIndexDeb822(pathname, records, addons, progress):
  401.     """
  402.     Create a new Xapian index with the content provided by the addons
  403.     """
  404.     progress.begin("Rebuilding Xapian index")
  405.  
  406.     # Create a new Xapian index
  407.     db = xapian.WritableDatabase(pathname, xapian.DB_CREATE_OR_OVERWRITE)
  408.     # It seems to be faster without transactions, at the moment
  409.     #db.begin_transaction(False)
  410.  
  411.     # Iterate all Debian packages
  412.  
  413.     # force apt to not write a pkgcache.bin
  414.     cache = apt.Cache(memonly=True)
  415.     count = len(cache)
  416.     for idx, pkg in enumerate(records):
  417.         # Print progress
  418.         if idx % 200 == 0: progress.progress(100*idx/count)
  419.  
  420.         document = xapian.Document()
  421.  
  422.         # The document data is the package name
  423.         document.set_data(pkg["Package"])
  424.  
  425.         # Index the package name with a special prefix, to be able to find this
  426.         # document by exact package name match
  427.         document.add_term("XP"+pkg["Package"])
  428.  
  429.         # Have all the various plugins index their things
  430.         for addon in addons:
  431.             addon.obj.indexDeb822(document, pkg)
  432.  
  433.         # Add the document to the index
  434.         db.add_document(document)
  435.     #db.commit_transaction();
  436.     db.flush()
  437.     progress.end()
  438.  
  439.  
  440. def writeValues(pathname, values, values_desc, progress):
  441.     """
  442.     Write the value information on the given file
  443.     """
  444.     progress.verbose("Writing value information to %s." % pathname)
  445.     out = open(pathname+".tmp", "w")
  446.  
  447.     print >>out, textwrap.dedent("""
  448.     # This file contains the mapping between names of numeric values indexed in the
  449.     # APT Xapian index and their index
  450.     #
  451.     # Xapian allows to index numeric values as well as keywords and to use them for
  452.     # all sorts of useful querying tricks.  However, every numeric value needs to
  453.     # have a unique index, and this configuration file is needed to record which
  454.     # indices are allocated and to provide a mnemonic name for them.
  455.     #
  456.     # The format is exactly like /etc/services with name, number and optional
  457.     # aliases, with the difference that the second column does not use the
  458.     # "/protocol" part, which would be meaningless here.
  459.     """).lstrip()
  460.  
  461.     for name, idx in sorted(values.iteritems(), key=lambda x: x[1]):
  462.         desc = values_desc[name]
  463.         print >>out, "%s\t%d\t# %s" % (name, idx, desc)
  464.  
  465.     out.close()
  466.     # Atomic update of the documentation
  467.     os.rename(pathname+".tmp", pathname)
  468.  
  469. def writeDoc(pathname, addons, progress):
  470.     """
  471.     Write the documentation in the given file
  472.     """
  473.     progress.verbose("Writing documentation to %s." % pathname)
  474.     # Collect the documentation
  475.     docinfo = []
  476.     for addon in addons:
  477.         try:
  478.             doc = addon.obj.doc()
  479.             if doc != None:
  480.                 docinfo.append(dict(
  481.                     name = doc['name'],
  482.                     shortDesc = doc['shortDesc'],
  483.                     fullDoc = doc['fullDoc']))
  484.         except:
  485.             # If a plugin has problem returning documentation, don't worry about it
  486.             progress.notice("Skipping documentation for plugin", addon.filename)
  487.  
  488.     # Write the documentation in pathname
  489.     out = open(pathname+".tmp", "w")
  490.     print >>out, textwrap.dedent("""
  491.     ===============
  492.     Database layout
  493.     ===============
  494.  
  495.     This Xapian database indexes Debian package information.  To query the
  496.     database, open it as ``%s/index``.
  497.  
  498.     Data are indexed either as terms or as values.  Words found in package
  499.     descriptions are indexed lowercase, and all other kinds of terms have an
  500.     uppercase prefix as documented below.
  501.  
  502.     Numbers are indexed as Xapian numeric values.  A list of the meaning of the
  503.     numeric values is found in ``%s``.
  504.  
  505.     The data sources used for indexing are:
  506.     """).lstrip() % (XAPIANDBPATH, XAPIANDBVALUES)
  507.  
  508.     for d in docinfo:
  509.         print >>out, " * %s: %s" % (d['name'], d['shortDesc'])
  510.  
  511.     print >>out, textwrap.dedent("""
  512.     This Xapian index follows the conventions for term prefixes described in
  513.     ``/usr/share/doc/xapian-omega/termprefixes.txt.gz``.
  514.  
  515.     Extra Debian data sources can define more extended prefixes (starting with
  516.     ``X``): their meaning is documented below together with the rest of the data
  517.     source documentation.
  518.  
  519.     At the very least, at least the package name (with the ``XP`` prefix) will
  520.     be present in every document in the database.  This allows to quickly
  521.     lookup a Xapian document by package name.
  522.  
  523.     The user data associated to a Xapian document is the package name.
  524.  
  525.  
  526.     -------------------
  527.     Active data sources
  528.     -------------------
  529.  
  530.     """)
  531.     for d in docinfo:
  532.         print >>out, d['name']
  533.         print >>out, '='*len(d['name'])
  534.         print >>out, textwrap.dedent(d['fullDoc'])
  535.         print >>out
  536.  
  537.     out.close()
  538.     # Atomic update of the documentation
  539.     os.rename(pathname+".tmp", pathname)
  540.  
  541.  
  542. #
  543. # Main program body
  544. #
  545.  
  546. from optparse import OptionParser
  547. import sys
  548.  
  549. VERSION="0.19"
  550.  
  551. class Parser(OptionParser):
  552.     def __init__(self, *args, **kwargs):
  553.         OptionParser.__init__(self, *args, **kwargs)
  554.  
  555.     def error(self, msg):
  556.         sys.stderr.write("%s: error: %s\n\n" % (self.get_prog_name(), msg))
  557.         self.print_help(sys.stderr)
  558.         sys.exit(2)
  559.  
  560. parser = Parser(usage="usage: %prog [options]",
  561.                 version="%prog "+ VERSION,
  562.                 description="Rebuild the Apt Xapian index")
  563. parser.add_option("-q", "--quiet", action="store_true", help="quiet mode: only output fatal errors")
  564. parser.add_option("-v", "--verbose", action="store_true", help="verbose mode")
  565. parser.add_option("-f", "--force", action="store_true", help="force database rebuild even if it's already up to date")
  566. parser.add_option("--pkgfile", action="store", help="do not use the APT cache, but the given Package file")
  567. parser.add_option("--batch-mode", action="store_true", help="use progress reporting suitable from programatic parsing.")
  568. parser.add_option("-u","--update", action="store_true", help="incremental update, reindexing only those packages whose version has changed since the last run")
  569. (options, args) = parser.parse_args()
  570.  
  571.  
  572. # Here starts the main functionality.  Imports things here so we can do --help
  573. # without requiring lots of dependencies (this helps at least help2man at
  574. # package build time)
  575. import warnings
  576. # Yes, apt, thanks, I know, the api isn't stable, thank you so very much
  577. #warnings.simplefilter('ignore', FutureWarning)
  578. warnings.filterwarnings("ignore","apt API not stable yet")
  579. import apt
  580. warnings.resetwarnings()
  581. import os.path, re, imp, glob, xapian, textwrap, shutil, fcntl, errno, itertools, time
  582. import socket, errno
  583. import cPickle as pickle
  584.  
  585. #if options.quiet: print "quiet"
  586. #if options.verbose: print "verbose"
  587. #if options.force: print "force"
  588.  
  589. # Instantiate the progress report
  590. if options.batch_mode:
  591.     progress = BatchProgress()
  592. elif options.quiet:
  593.     progress = SilentProgress()
  594. else:
  595.     progress = Progress()
  596.  
  597. if options.verbose:
  598.     progress.is_verbose = True
  599.  
  600. # Create the database directory if missing
  601. try:
  602.     # Try to create it anyway
  603.     os.mkdir(XAPIANDBPATH)
  604. except OSError, e:
  605.     if e.errno != errno.EEXIST:
  606.         # If we got an error besides path already existing, fail
  607.         raise
  608.     elif not os.path.isdir(XAPIANDBPATH):
  609.         # If that path already exists, but is not a directory, also fail
  610.         raise
  611.  
  612. # Lock the session so that we prevent concurrent updates
  613. lockfd = os.open(XAPIANDBLOCK, os.O_RDWR | os.O_CREAT)
  614. lockpyfd = os.fdopen(lockfd)
  615. try:
  616.     fcntl.lockf(lockpyfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
  617.     progress = ServerProgress(progress)
  618. except IOError, e:
  619.     if e.errno == errno.EACCES or e.errno == errno.EAGAIN:
  620.         progress.notice("Another update is already running: showing its progress.")
  621.         childProgress = ClientProgress(progress)
  622.         childProgress.loop()
  623.         sys.exit(0)
  624.     else:
  625.         raise
  626.  
  627. # Read values database
  628. #values = readValueDB(VALUESCONF, progress)
  629.  
  630. # Read the addons, in sorted order
  631. addons = readPlugins(PLUGINDIR, progress)
  632.  
  633. # Ensure that we have something to do
  634. if len(addons) == 0:
  635.     progress.notice("No indexing plugins found in %s" % PLUGINDIR)
  636.     sys.exit(1)
  637.  
  638. # Get the most recent modification timestamp of the data sources
  639. ds_timestamp = max([x.info['timestamp'] for x in addons])
  640.  
  641. # Get the timestamp of the last database update
  642. try:
  643.     cur_timestamp = os.path.getmtime(XAPIANDBSTAMP)
  644. except OSError, e:
  645.     cur_timestamp = 0
  646.     progress.notice("Reading current timestamp failed: %s. Assuming the index has not been created yet." % e)
  647.  
  648. if options.verbose:
  649.     progress.verbose("Most recent dataset:    %s." % time.ctime(ds_timestamp))
  650.     progress.verbose("Most recent update for: %s." % time.ctime(cur_timestamp))
  651.  
  652. # See if we need an update
  653. if ds_timestamp <= cur_timestamp:
  654.     if options.force:
  655.         progress.notice("The index %s is up to date, but rebuilding anyway as requested." % XAPIANDBPATH)
  656.     else:
  657.         progress.notice("The index %s is up to date" % XAPIANDBPATH)
  658.         sys.exit(0)
  659.  
  660. # Build the value database
  661. progress.verbose("Aggregating value information.")
  662. values = dict(version=0)
  663. values_seq = 1
  664. values_desc = dict(version="package version")
  665. for addon in addons:
  666.     for v in addon.info.get("values", []):
  667.         values[v['name']] = values_seq
  668.         values_seq += 1
  669.         values_desc[v['name']] = v['desc']
  670.  
  671. # Tell the addons to do the long initialisation bits
  672. progress.verbose("Initializing plugins.")
  673. for addon in addons:
  674.     addon.obj.init(dict(values = values), progress)
  675.  
  676. # update only mode
  677. if options.update:
  678.     index = os.path.join(os.path.abspath(XAPIANDBPATH), "index")
  679.     if not os.path.exists(index):
  680.         f=open(index, "w")
  681.         f.write("auto %s.1" % index)
  682.         f.close()
  683.     (dbkind, dbpath) = open(index).readline().split()
  684.     res = updateIndex(dbpath, addons, progress)
  685.     if not res:
  686.         sys.exit(1)
  687.     # touch the index to update the timestamp
  688.     open(XAPIANDBSTAMP, "w").close()
  689.     sys.exit()
  690.  
  691. # Create a new Xapian index with the content provided by the addons
  692. # Xapian takes care of preventing concurrent updates and removing the old
  693. # database if it's left over by a previous crashed update
  694.  
  695. # Create a temporary file name
  696. for idx in itertools.count(1):
  697.     tmpidxfname = "index.%d" % idx
  698.     dbdir = XAPIANDBPATH + "/" + tmpidxfname
  699.     if not os.path.exists(dbdir): break;
  700.  
  701. if options.pkgfile:
  702.     from debian_bundle import deb822
  703.     buildIndexDeb822(dbdir, deb822.Deb822.iter_paragraphs(open(options.pkgfile)), addons, progress)
  704. else:
  705.     buildIndex(dbdir, addons, progress)
  706.  
  707. # Update the 'index' symlink to point at the new index
  708. progress.verbose("Installing the new index.")
  709. try:
  710.     os.unlink(XAPIANDBPATH + "/index.tmp")
  711. except OSError:
  712.     # Ignore the error here: we're deleting it 'just in case', because symlink
  713.     # wouldn't delete it itself
  714.     pass
  715.  
  716. #os.symlink(tmpidxfname, XAPIANDBPATH + "/index.tmp")
  717. out = open(XAPIANDBPATH + "/index.tmp", "w")
  718. print >>out, "auto", os.path.join(os.path.abspath(XAPIANDBPATH), tmpidxfname)
  719. out.close()
  720. os.rename(XAPIANDBPATH + "/index.tmp", XAPIANDBPATH + "/index")
  721.  
  722. # Remove all other index.* directories that are not the newly created one
  723. for file in os.listdir(XAPIANDBPATH):
  724.     if not file.startswith("index."): continue
  725.     # Only delete directories
  726.     if not os.path.isdir(XAPIANDBPATH + "/" + file): continue
  727.     # Don't delete what we just created
  728.     if file == tmpidxfname: continue
  729.     fullpath = XAPIANDBPATH + "/" + file
  730.     progress.verbose("Removing old index %s." % fullpath)
  731.     shutil.rmtree(fullpath)
  732.  
  733. # Commit the changes and update the last update timestamp
  734. if not os.path.exists(XAPIANDBSTAMP):
  735.     open(XAPIANDBSTAMP, "w").close()
  736. os.utime(XAPIANDBSTAMP, (ds_timestamp, ds_timestamp))
  737.  
  738. writeValues(XAPIANDBVALUES, values, values_desc, progress)
  739. writeDoc(XAPIANDBDOC, addons, progress)
  740.  
  741. sys.exit(0)
  742.